package com.lw.config;

import com.lw.pojo.Bgm;
import com.lw.service.VideoServiceImpl;
import com.lw.utils.JsonUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

@Component
public class ZKCuratorClient {
    // zk客户端
    private CuratorFramework client = null;
    final static Logger log = LoggerFactory.getLogger(ZKCuratorClient.class);

    public static final String ZOOKEEPER_SERVER = "192.168.1.210:2181";

    @Autowired
    private ResourceConfig resourceConfig;

    @Autowired
    private VideoServiceImpl videoService;


    public void init() {

        if (client != null) {
            return;
        }

        // 重试策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
        // 创建zk客户端，使用/admin路径
        client = CuratorFrameworkFactory.builder().connectString(resourceConfig.getZookeeperServer())
                .sessionTimeoutMs(10000).retryPolicy(retryPolicy).namespace("admin").build();
        // 启动客户端
        client.start();

        try {
//			String testNodeData = new String(client.getData().forPath("/bgm/18052674D26HH3X4"));
//			log.info("测试的节点数据为： {}", testNodeData);
            addChildWatch("/bgm");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 监听节点
     *
     * @param nodePath
     * @throws Exception
     */
    public void addChildWatch(String nodePath) throws Exception {

        final PathChildrenCache cache = new PathChildrenCache(client, nodePath, true);
        cache.start();
        //添加监听器
        cache.getListenable().addListener(new PathChildrenCacheListener() {

            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
                    throws Exception {

                /**
                 * 1. 监听节点添加、更新、删除信息
                 *      添加：可能是新增bgm,也可能是删除bgm，无法确定。需要通过 operatorType 确定
                 *      更新： 也无法确定，需要通过 operatorType 确定
                 *      删除：删除节点可以确定是需要删除bgm，获取到被删除的节点，用 operatorType 可能是add，也可能是delete。
                 *           假如管理台服务新增bgm，新增了节点，operatorType=add
                 *           然后管理台服务删除bgm，此时 operatorType获取的值是add，但其实应该是要删除本地的bgm，以保证和管理台服务同步。
                 *           所以这里可以约定，节点不由管理控制台删除，而由监听端删除。
                 *
                 *  2. 获取到path，/bgm/{bgmId}, operatorType是操作类型
                 *  3. 通过bgmId去数据库查询Bgm对象信息
                 *         但这里有个问题： 如果操作是add的情况，bgm上传信息是在管理控制台，是管理控制台所在的数据库，
                 *         可能和本项目连的不是同一个数据库。
                 *         所以这里面查询bgm信息可能通过数据库，也可能通过rpc调用管理控制台服务
                 *  4. 获取到bgm信息后，如果是add则调用管理控制台服务API下载音乐，
                 *      如果是delete，则调用本地服务删除音乐，删除本地数据库信息，删除对应节点
                 */


                //监听添加节点、节点数据更新
                if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)
                        || event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)) {
                    log.info("监听到事件：" + event.getType().name());

                    // path: /bgm/{bgmId}
                    String path = event.getData().getPath();
                    //delete , add
                    String operatorType = new String(event.getData().getData());

                    //从数据库查询bgm对象，获取路径path
                    String arr[] = path.split("/");
                    String bgmId = arr[arr.length - 1];

                    Bgm bgm = videoService.queryBgmById(bgmId,operatorType);
                    if (bgm == null) {
                        return;
                    }
                    //bgm所在的相对路径  songPath: /bgms/cc.mp3
                    String songPath = bgm.getPath();

                    // 1. updated 修改为从zk获取
//                    String operatorObjStr = new String(event.getData().getData(), "UTF-8");
//                    Map<String, String> map = JsonUtils.jsonToPojo(operatorObjStr, Map.class);
//                    String operatorType = map.get("operType");
//                    String songPath = map.get("path");

                    //定义保存到本地的bgm路径 filePath: /temp/user/files/bgms/cc.mp3
                    String filePath = resourceConfig.getFileSpace() + songPath;
                    //定义下载的路径（播放url）
                    String arrPath[] = songPath.split("/");            // linux
//					String arrPath[] = songPath.split("\\\\");		// windows
                    String finalPath = "";
                    //处理url的斜杠以及编码
                    for (int i = 0; i < arrPath.length; i++) {
                        if (StringUtils.isNotBlank(arrPath[i])) {
                            finalPath += "/";
                            finalPath += URLEncoder.encode(arrPath[i], "UTF-8");
                        }
                    }
                    //finalPath: /bgms/cc.mp3

                    //bgmUrl: http://localhost:8080/AdminApi/bgms/cc.mp3 , 管理控制台处配置的Tomcat虚拟目录，保存音乐文件的地方
                    String bgmUrl = resourceConfig.getBgmServer() + finalPath;

                    if ("add".equals(operatorType)) {
                        // 下载bgm到springboot服务器
                        URL url = new URL(bgmUrl);
                        File file = new File(filePath);
                        FileUtils.copyURLToFile(url, file);
                        client.delete().forPath(path);
                    } else if ("delete".equals(operatorType)) {
                        File file = new File(filePath);
                        FileUtils.forceDelete(file);//删除本地文件
                        client.delete().forPath(path); //删除zookeeper节点
                        //删除本地数据库中数据，此处省略代码
                    }
                }
            }
        });
    }


}
